package com.poetize.plus.config;

import com.poetize.plus.filter.CheckTokenFilter;
import com.poetize.plus.handler.*;
import com.poetize.plus.properties.SecurityProperties;
import com.poetize.plus.service.impl.UserDetailsServiceImpl;
import jakarta.annotation.security.PermitAll;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.Set;

/**
 *
 * security配置
 *
 * @author zuosy
 * @created 2024/3/28 15:37
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final LogOutHandler logOutHandler;

    private final CheckTokenFilter checkTokenFilter;

    private final LoginFiledHandler loginFiledHandler;

    private final SecurityProperties securityProperties;

    private final LoginSuccessHandler loginSuccessHandler;

    private final LoginAccessDefineHandler loginAccessDefineHandler;

    private final LoginAuthenticationHandler loginAuthenticationHandler;

    private final UserDetailsServiceImpl userDetailsService;

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * 密码处理器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    /**
     * AuthenticationProvider是认证逻辑的提供者
     *
     * @return
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    @Bean
    @Order(2)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //@PermitAll注解不校验的接口
        Set<String> permitAllAnnotation = getUrlsFormPermitAllAnnotation();
        //白名单
        permitAllAnnotation.addAll(securityProperties.getWhiteUrl());
        String[] array = permitAllAnnotation.toArray(String[]::new);
        //security核心配置
        http    // 使用自己自定义的过滤器 去过滤接口请求
                .addFilterBefore(checkTokenFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin(conf -> conf.successHandler(loginSuccessHandler)
                        .failureHandler(loginFiledHandler))
                .logout(conf -> conf.logoutSuccessHandler(logOutHandler))
                //禁用cors
                .cors(AbstractHttpConfigurer::disable)
                //禁用csrf
                .csrf(AbstractHttpConfigurer::disable)
                // 配置了会话管理策略为 STATELESS（无状态）。在无状态的会话管理策略下，应用程序不会创建或使用 HTTP 会话，每个请求都是独立的，服务器不会在请求之间保留任何状态信息。
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests((authorize) ->
                        authorize.requestMatchers(array).permitAll()
                                .anyRequest().authenticated())
                //自定义处理器
                .exceptionHandling(configure ->
                        configure.authenticationEntryPoint(loginAuthenticationHandler)//未登录处理器
                                .accessDeniedHandler(loginAccessDefineHandler)//无权限处理器
                )
                .headers((header) -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
        return http.build();
    }

    /**
     * 获取被@PermitAll注解修饰的url地址
     */
    private Set<String> getUrlsFormPermitAllAnnotation() {
        Set<String> result = new HashSet<>();
        requestMappingHandlerMapping.getHandlerMethods().forEach((key, method) -> {
            if (method.hasMethodAnnotation(PermitAll.class)) {
                Set<String> urls = key.getPatternValues();
                if (!CollectionUtils.isEmpty(urls)) {
                    result.addAll(urls);
                }
            }
        });
        return result;
    }
}
